class OnvifManager {
    constructor() {
        this.ws = null;
        this.el = {
            'frm_con': $('#connect-form'),
            'sel_dev': $('#connect-form select[name="device"]'),
            'inp_usr': $('#connect-form input[name="user"]'),
            'inp_pas': $('#connect-form input[name="pass"]'),
            'btn_con': $('#connect-form button[name="connect"]'),
            'div_pnl': $('#connected-device'),
            'img_snp': $('#connected-device img.snapshot'),
            'btn_dcn': $('#connected-device button[name="disconnect"]'),
            'mdl_msg': $('#message-modal'),
            'ptz_spd': $('input[name="ptz-speed"]'),
            'btn_hme': $('#connected-device div.ptz-pad-box button.ptz-goto-home'),
            'ptz_pad': $('#connected-device div.ptz-pad-box'),
            'zom_in': $('#connected-device div.ptz-zom-ctl-box button.ptz-zom-in'),
            'zom_out': $('#connected-device div.ptz-zom-ctl-box button.ptz-zom-ot'),
        };
        this.selected_address = '';
        this.device_connected = false;
        this.ptz_moving = false;
        this.snapshot_w = 400;
        this.snapshot_h = 300;
    }

    init() {
        this.initWebSocketConnection();
        $(window).on('resize', this.adjustSize.bind(this));
        this.el['btn_con'].on('click', this.pressedConnectButton.bind(this));
        this.el['btn_dcn'].on('click', this.pressedConnectButton.bind(this));
        $(document.body).on('keydown', this.ptzMove.bind(this));
        $(document.body).on('keyup', this.ptzStop.bind(this));
        this.el['btn_hme'].on('click', this.ptzGotoHome.bind(this));
        this.el['btn_hme'].on('touchstart', this.ptzGotoHome.bind(this));
        this.el['btn_hme'].on('touchend', this.ptzGotoHome.bind(this));
        this.el['ptz_pad'].on('mousedown', this.ptzMove.bind(this));
        this.el['ptz_pad'].on('mouseup', this.ptzStop.bind(this));
        this.el['ptz_pad'].on('touchstart', this.ptzMove.bind(this));
        this.el['ptz_pad'].on('touchend', this.ptzStop.bind(this));
        this.el['zom_in'].on('mousedown', this.ptzMove.bind(this));
        this.el['zom_in'].on('mouseup', this.ptzStop.bind(this));
        this.el['zom_in'].on('touchstart', this.ptzMove.bind(this));
        this.el['zom_in'].on('touchend', this.ptzStop.bind(this));
        this.el['zom_out'].on('mousedown', this.ptzMove.bind(this));
        this.el['zom_out'].on('mouseup', this.ptzStop.bind(this));
        this.el['zom_out'].on('touchstart', this.ptzMove.bind(this));
        this.el['zom_out'].on('touchend', this.ptzStop.bind(this));
    }

    adjustSize() {
        let div_dom_el = this.el['div_pnl'].get(0);
        let rect = div_dom_el.getBoundingClientRect();
        let x = rect.left + window.pageXOffset;
        let y = rect.top + window.pageYOffset;
        let w = rect.width;
        let h = window.innerHeight - y - 10;
        div_dom_el.style.height = h + 'px';
        let aspect_ratio = w / h;
        let snapshot_aspect_ratio = this.snapshot_w / this.snapshot_h;
        let img_dom_el = this.el['img_snp'].get(0);
        let img_w, img_h;
        if (snapshot_aspect_ratio > aspect_ratio) {
            img_w = w;
            img_h = (w / snapshot_aspect_ratio);
            img_dom_el.style.width = img_w + 'px';
            img_dom_el.style.height = img_h + 'px';
            img_dom_el.style.left = '0px';
            img_dom_el.style.top = ((h - img_h) / 2) + 'px';
        } else {
            img_h = h;
            img_w = (h * snapshot_aspect_ratio);
            img_dom_el.style.height = img_h + 'px';
            img_dom_el.style.width = img_w + 'px';
            img_dom_el.style.left = ((w - img_w) / 2) + 'px';
            img_dom_el.style.top = '0px';
        }
    }
    initWebSocketConnection() {
        this.ws = new WebSocket('ws://' + document.location.host);
        this.ws.onopen = () => {
            console.log('WebSocket connection established.');
            this.sendRequest('startDiscovery');
        };
        this.ws.onclose = (event) => {
            console.log('WebSocket connection closed.');
            this.showMessageModal('Error', 'The WebSocket connection was closed. Check if the server.js is running.');
        };
        this.ws.onerror = (error) => {
            this.showMessageModal('Error', 'Failed to establish a WebSocket connection. Check if the server.js is running.');
        };
        this.ws.onmessage = (res) => {
            const data = JSON.parse(res.data);
            const id = data.id;
            if (id === 'startDiscovery') {
                this.startDiscoveryCallback(data);
            } else if (id === 'connect') {
                this.connectCallback(data);
            } else if (id === 'fetchSnapshot') {
                this.fetchSnapshotCallback(data);
            } else if (id === 'ptzMove') {
                this.ptzMoveCallback(data);
            } else if (id === 'ptzStop') {
                this.ptzStopCallback(data);
            } else if (id === 'ptzHome') {
                this.ptzHomeCallback(data);
            }
        };
    };
    sendRequest(method, params) {
        this.ws.send(JSON.stringify({
            'method': method,
            'params': params
        }));
    };

    pressedConnectButton(event) {
        if (this.device_connected === true) {
            this.disconnectDevice();
        } else {
            this.connectDevice();
        }
    };

    disconnectDevice() {
        this.el['img_snp'].removeAttr('src');
        this.el['div_pnl'].hide();
        this.el['frm_con'].show();
        this.device_connected = false;
        this.disabledLoginForm(false);
        this.el['btn_con'].text('Connect');
    };

    connectDevice() {
        this.disabledLoginForm(true);
        this.el['btn_con'].text('Connecting...');
        this.sendRequest('connect', {
            'address': this.el['sel_dev'].val(),
            'user': this.el['inp_usr'].val(),
            'pass': this.el['inp_pas'].val(),
            rtsp: 'rtsp://admin:zl@123456@172.16.16.70:554/Streaming/Channels/101',
        });
    }

    disabledLoginForm(disabled) {
        this.el['sel_dev'].prop('disabled', disabled);
        this.el['inp_usr'].prop('disabled', disabled);
        this.el['inp_pas'].prop('disabled', disabled);
        this.el['btn_con'].prop('disabled', disabled);
    };

    startDiscoveryCallback(data) {
        let devices = data.result;
        this.el['sel_dev'].empty();
        this.el['sel_dev'].append($('<option>Select a device</option>'));
        let n = 0;
        for (let key in devices) {
            let device = devices[key];
            let option_el = $('<option></option>');
            option_el.val(device.address);
            option_el.text(device.name + ' (' + device.address + ')');
            this.el['sel_dev'].append(option_el);
            n++;
        }
        if (n === 0) {
            this.showMessageModal('Error', 'No device was found. Reload this page to discover ONVIF devices again.')
        } else {
            this.disabledLoginForm(false);
        }
    };

    connectCallback(data) {
        this.el['btn_con'].prop('disabled', false);
        if (data.result) {
            this.selected_address = this.el['sel_dev'].val();
            this.showConnectedDeviceInfo(this.selected_address, data.result);
            this.el['btn_con'].text('Disconnect');
            this.el['frm_con'].hide();
            this.el['div_pnl'].show();
            this.device_connected = true;
        } else if (data.error) {
            this.el['div_pnl'].hide();
            this.el['sel_dev'].prop('disabled', false);
            this.el['inp_usr'].prop('disabled', false);
            this.el['inp_pas'].prop('disabled', false);
            this.el['btn_con'].text('Connect');
            this.el['frm_con'].show();
            this.showMessageModal('Error', 'Failed to connect to the device.' + data.error.toString());
            this.device_connected = false;
        }
    };

    showMessageModal(title, message) {
        this.el['mdl_msg'].find('.modal-title').text(title);
        this.el['mdl_msg'].find('.modal-message').text(message);
        this.el['mdl_msg'].modal('show');
    };

    showConnectedDeviceInfo(address, data) {
        this.el['div_pnl'].find('span.name').text(data['Manufacturer'] + ' ' + data['Model']);
        this.el['div_pnl'].find('span.address').text(address);
        this.fetchSnapshot();
    };

    fetchSnapshot() {
        this.sendRequest('fetchSnapshot', {
            'address': this.selected_address
        });
    };

    fetchSnapshotCallback(data) {
        if (data.result) {
            this.el['img_snp'].attr('src', data.result);
            window.setTimeout(function () {
                this.snapshot_w = this.el['img_snp'].get(0).naturalWidth;
                this.snapshot_h = this.el['img_snp'].get(0).naturalHeight;
                this.adjustSize();
                if (this.device_connected === true) {
                    this.fetchSnapshot();
                }
            }.bind(this), 10);
        } else if (data.error) {
            console.log(data.error);
        }
    };

    ptzGotoHome(event) {
        event.preventDefault();
        event.stopPropagation();
        if (event.type === 'touchstart') {
            return;
        }
        if (this.device_connected === false || this.ptz_moving === true) {
            return;
        }
        this.ptz_moving = true;
        this.sendRequest('ptzHome', {
            'address': this.selected_address,
            'timeout': 30
        });
    };
    getPresets(token) {
        this.sendRequest('getPresets', {
            'address': this.selected_address,
            token
        });
    }
    setPreset(token) {
        this.sendRequest('setPreset', {
            'address': this.selected_address,
            token
        });
    }
    gotoPreset(token) {
        this.sendRequest('gotoPreset', {
            'address': this.selected_address,
            token
        });
    }
    removePreset(token) {
        console.log(3223)
        this.sendRequest('removePreset', {
            'address': this.selected_address,
            token
        });
    }
    reboot() {
        this.sendRequest('reboot', {
            'address': this.selected_address,
        });
    }
    ptzMove(event) {
        if (this.device_connected === false || this.ptz_moving === true) {
            return;
        }
        this.ptz_moving = true;
        let pos = { x: 0, y: 0, z: 0 };
        let speed = 1.0;

        if (event.type === 'keydown') {
            this.el['ptz_spd'].each(function (index, el) {
                if ($(el).prop('checked') === true) {
                    speed = parseFloat($(el).val());
                }
            }.bind(this));
            let c = event.keyCode;
            let s = event.shiftKey;
            if (c === 38) { // Up
                pos.y = speed;
            } else if (c === 40) { // Down
                pos.y = 0 - speed;
            } else if (c === 37) { // Left
                pos.x = 0 - speed;
            } else if (c === 39) { // Right
                pos.x = speed;
            } else if ((c === 107) || c === 187) { // Zoom in
                pos.z = speed;
            } else if (c === 109 || c === 189) { // Zoom out
                pos.z = 0 - speed;
            } else {
                return;
            }
        } else if (event.type.match(/^(mousedown|touchstart)$/)) {
            if (event.currentTarget.classList.contains('ptz-pad-box')) {
                let rect = event.currentTarget.getBoundingClientRect();
                let cx = event.clientX;
                let cy = event.clientY;
                if (event.type === 'touchstart') {
                    if (event.targetTouches[0]) {
                        cx = event.targetTouches[0].clientX;
                        cy = event.targetTouches[0].clientY;
                    } else if (event.changedTouches[0]) {
                        cx = event.changedTouches[0].clientX;
                        cy = event.changedTouches[0].clientY;
                    }
                }
                let mx = cx - rect.left;
                let my = cy - rect.top;
                let w = rect.width;
                let h = rect.height;
                let r = Math.max(w, h) / 2;
                let x = mx - r;
                let y = r - my;
                let d = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) / r;
                let rad = Math.atan2(y, x);
                pos.x = d * Math.cos(rad);
                pos.y = d * Math.sin(rad);
            } else if (event.currentTarget.classList.contains('ptz-zom')) {
                if (event.currentTarget.classList.contains('ptz-zom-ot')) {
                    pos.z = -1.0;
                } else if (event.currentTarget.classList.contains('ptz-zom-in')) {
                    pos.z = 1.0;
                } else {
                    return;
                }
            } else {
                return;
            }
        } else {
            return;
        }

        this.sendRequest('ptzMove', {
            'address': this.selected_address,
            'speed': pos,
            'timeout': 30
        });
        event.preventDefault();
        event.stopPropagation();
    };

    ptzStop(event) {
        let c = event.keyCode;
        let keys = [37,38,39,40,107,187,109,189]
        console.log(keys.indexOf(c), c)
        if (keys.indexOf(c) == -1) return;
        if (!this.selected_address) {
            return;
        }
        this.sendRequest('ptzStop', {
            'address': this.selected_address
        });
        this.ptz_moving = false;
    };

    ptzMoveCallback(data) {
        // do nothing
    };

    ptzStopCallback(data) {
        // do nothing
    };

    ptzHomeCallback(data) {
        // do nothing
    };
}

$(document).ready(function () {
    new OnvifManager().init();
});